package com.chen.sample1.web.aspect;

import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.chen.sample1.common.dto.RequestLogDto;
import com.chen.sample1.common.message.ResponseMsg;
import lombok.SneakyThrows;
import lombok.var;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;

/**
 * @desc 请求日志拦截切面
 * @Author Chentian
 * @date 2021/9/7
 */
@Component
@Aspect
@Order(-1) //@Order标记定义了组件的加载顺序，值越小拥有越高的优先级，可为负数。
public class LogAspect {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    private static String[] BASE_TYPES = {"java.lang.Integer", "java.lang.Double",
            "java.lang.Float", "java.lang.Long", "java.lang.Short",
            "java.lang.Byte", "java.lang.Boolean", "java.lang.Char",
            "java.lang.String", "int", "double", "long", "short", "byte",
            "boolean", "char", "float"};

    /**
     * 定义一个切入点.
     * 解释下：
     * <p>
     * ~ 第一个 * 代表任意修饰符及任意返回值.
     * ~ 第二个 * 任意包名
     * ~ 第三个 * 代表任意方法.
     * ~ 第四个 * 定义在controller包或者子包
     * ~ 第五个 * 任意方法
     * ~ .. 匹配任意数量的参数.
     */
    @Pointcut(value = "execution(public * com.chen.sample1.web..*.*Controller.*(..)))")
    public void filterController() {
        // nothing to do
    }

    @Pointcut(value = "!execution(public * com.chen.sample1.web..*.*Controller.query*(..)))")
    public void filterNotQuery() {
        // nothing to do
    }

    @Pointcut(value = "!execution(public * com.chen.sample1.web..*.*Controller.get*(..)))")
    public void filterNotGet() {
        // nothing to do
    }

    @Pointcut(value = "filterController() && filterNotQuery() && filterNotGet()")
    public void filterCustom() {
        // nothing to do
    }


    @SneakyThrows
    @Around(value = "filterController()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        // before
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();
        String classMethod = joinPoint.getSignature().getDeclaringType().getCanonicalName() + "." + joinPoint.getSignature().getName();

        // 获取浏览器agent信息
        String uaStr = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgentUtil.parse(uaStr);

        // 请求参数及请求值
        String argStr = mergeParamsAndArgsToStr(joinPoint);

        // 执行方法
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();

        // after
        //请求结果
        ResponseMsg responseMsg = Convert.convert(ResponseMsg.class, result);

        //todo 通过 token 获取访问用户信息

        RequestLogDto requestLogDto = RequestLogDto.builder()
                .url(url)
                .classMethod(classMethod)
                .args(argStr)
                .ip(request.getRemoteAddr())
                .deviceType(userAgent.getOs().toString())
                .os(userAgent.getOs().getName())
                .mobileFlag(userAgent.isMobile())
                .browser(userAgent.getBrowser().getName())
                .duration(endTime - startTime)
                .code(responseMsg.getCode())
                .message(responseMsg.getDescription())
                .result(String.valueOf(responseMsg.getData()))
                .createBy(null)
                .createTime(new Date(startTime))
                .build();

        logger.info("requestLogDto:{}",requestLogDto);
        return result;
    }

    /**
     * 组合参数名和参数值为字符串
     */
    private String mergeParamsAndArgsToStr(ProceedingJoinPoint joinPoint) {
        // 校验是否有无请求参数注解
        Signature signature = joinPoint.getSignature();
        var methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method.getAnnotation(NotLogParam.class) != null) {
            return null;
        }

        StringBuilder argsStr = new StringBuilder();
        // joinPoint获取参数名
        String[] params = ((CodeSignature) joinPoint.getStaticPart().getSignature()).getParameterNames();
        // joinPoint获取参数值
        Object[] args = joinPoint.getArgs();

        for (int i = 0; i < params.length ; i++) {
            Object arg = args[i];
            // 跳过 request 和 response 参数
            if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) {
                continue;
            }
            if(arg == null){
                argsStr.append("&").append(params[i]).append(" is null");
            }else {
                String typeName = arg.getClass().getTypeName();
                if (!Arrays.asList(BASE_TYPES).contains(typeName)) {
                    // 把参数转成json格式
                    argsStr.append("&").append(params[i]).append("=").append(String.valueOf(arg));
                } else {
                    argsStr.append("&").append(params[i]).append("=").append(arg);
                }
            }
        }

        if(argsStr.length() == 0){
            return null;
        }
        return argsStr.substring(1) ;
    }

}
